package reflection;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;

/**
 *
 * @author Hasier Rodriguez
 */
public class JarUtils {

    private static final JarClassLoader classLoader = new JarClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());

    private static <T> Collection<T> instantiateObjectsFromJarFilesAndNameAsAttribute(File[] jarFiles, String packagePrefix) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (jarFiles == null) {
            throw new NullPointerException("The given jar files array cannot be null.");
        }
        URL[] jarFileURLs = fromFilesToURLs(jarFiles);
        for (URL u : jarFileURLs) {
            classLoader.addURL(u);
        }
        ArrayList<T> instances = new ArrayList<T>();
        String[] attributeValuesForClassName = getInstantiationValuesFromJarFiles(jarFiles, packagePrefix);
        for (String attributeValueForClassName : attributeValuesForClassName) {
            Class<?> classObject = Class.forName(attributeValueForClassName, false, classLoader);
            Object instance = classObject.newInstance();
            instances.add((T) instance);
        }
        return instances;
    }

    private static URL[] fromFilesToURLs(File[] files) throws MalformedURLException {
        if (files == null) {
            throw new NullPointerException("The given file array cannot be null.");
        }
        URL[] urls = new URL[files.length];
        for (int i = 0; i < files.length; i++) {
            urls[i] = files[i].toURI().toURL();
        }
        return urls;
    }

    private static String[] getInstantiationValuesFromJarFiles(File[] jarFiles, String packagePrefix) throws IOException {
        if (jarFiles == null) {
            throw new NullPointerException("The given jar files array cannot be null.");
        }
        ArrayList<String> attributeValues = new ArrayList<String>();
        for (File file : jarFiles) {
            String name = file.getName();
            //Anyadir a la lista todos los nombres de los jars sin la extensión
            attributeValues.add(packagePrefix + "." + name.substring(0, name.length() - 4));
        }
        String[] attributeValuesArray = new String[attributeValues.size()];
        attributeValuesArray = attributeValues.toArray(attributeValuesArray);
        return attributeValuesArray;
    }

    private static File[] getFilesWithSpecificExtensionInDirectory(String directoryPath, final String extension) {
        if (directoryPath == null) {
            throw new NullPointerException("The given directory path string cannot be null.");
        }
        if (extension == null) {
            throw new NullPointerException("The given extension string cannot be null.");
        }
        File directoryPathFile = new File(directoryPath);
        if (!directoryPathFile.isDirectory()) {
            throw new IllegalArgumentException("The given folder path '" + directoryPath + "' is not a directory.");
        }
        // Implement and instantiate a filename filter using an anonymous class.
        FilenameFilter filenameFilter = new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                // The files with the extension in their names will be accepted in the filter.
                // It is case insensitive.
                return name.toUpperCase().endsWith(extension.toUpperCase());
            }
        };
        // Get the files using the filter.
        File[] filesWithSpecificExtension = directoryPathFile.listFiles(filenameFilter);
        return filesWithSpecificExtension;
    }

    /**
     * Devuelve el JarClassLoader que contiene todos los recursos cargados con esta clase
     *
     * @return El JarClassLoader con el que se puede cargar cualquier recurso dentro de ellos
     */
    public static JarClassLoader getClassLoader() {
        return classLoader;
    }

    /**
     * Método que devuelve una Collection<T> con todos los objetos de un path creados
     *
     * @param <T> Tipo de objeto que va a crear
     * @param path Path al lugar donde están los paquetes que contienen las clases a instanciar
     * @param extension La extensión de los archivos (.jar, .apk, ...)
     * @param prefix Prefijo del paquete (si la clase es es.deusto.clase, prefix es es.deusto)
     * @return Collection<T> con todos los objetos del path instanciados
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    public static <T> Collection<T> getObjectsAtDirectory(String path, String extension, String prefix) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        File[] jarFiles = getFilesWithSpecificExtensionInDirectory(path, extension);
        return instantiateObjectsFromJarFilesAndNameAsAttribute(jarFiles, prefix);
    }

    /**
     * Método que devuelve una instancia de la clase en el jar especificado
     *
     * @param <T> Tipo de objeto que va a crear
     * @param path Path al lugar donde está el jar
     * @param prefix Prefijo del paquete (si la clase es es.deusto.clase, prefix es es.deusto)
     * @return Collection<T> con todos los objetos del path instanciados
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    public static <T> Object getObjectFromJar(String jarPath, String prefix) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        Collection<T> c = instantiateObjectsFromJarFilesAndNameAsAttribute(new File[]{new File(jarPath)}, prefix);
        if (c != null && !c.isEmpty()) {
            return c.iterator().next();
        } else {
            return null;
        }
    }

    /**
     * Método que anyade al build path el jar especificado
     *
     * @param path Path al lugar donde está el jar
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    public static void addToBuildPath(String jarPath) throws MalformedURLException {
        classLoader.addURL(new File(jarPath).toURI().toURL());
    }
}
